package com.fast.linkbeanadmin.security.config;

import com.fast.linkbeanadmin.config.AuthorizationProperties;
import com.fast.linkbeanadmin.security.filter.TokenAuthenticationFilter;
import com.fast.linkbeanadmin.security.impl.LogoutHandlerImpl;
import com.fast.linkbeanadmin.utils.redis.RedisUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import java.util.Collections;

/**
 * @author ruan cai yuan
 * @version 1.0
 * @fileName com.fast.linkbeanadmin.security.config.SecurityConfig
 * @description: TODO
 * @since 2024/7/21 下午5:35
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private AuthenticationSuccessHandler successHandler;
    @Resource
    private AuthenticationFailureHandler failureHandler;
    @Resource
    private LogoutHandlerImpl logoutHandler;
    @Resource
    private AuthorizationProperties properties;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter(redisUtil);
    }

    /**
     * AuthenticationManagerBuilder用来构建AuthenticationManager,
     * ProviderManager是AuthenticationManager接口的实现,持有多个AuthenticationProvider
     * 用于认证传来的Authentication(
     * UsernamePasswordAuthenticationFilter#attemptAuthentication中的UsernamePasswordAuthenticationToken),
     * 并返回认证结果Authentication(
     * AbstractUserDetailsAuthenticationProvider#authenticate封装UserDetails为UsernamePasswordAuthenticationToken),
     * DaoAuthenticationProvider是AuthenticationProvider的一个实现,用于调用
     * userDetailsService.loadUserByUsername()
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用csrf &   配置跨域 & 禁用session
        http.csrf().disable().cors().configurationSource(configurationSource()).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //默认是/login  &  指定提交密码的参数名 & 提交方式是表单提交 ==》对应的filter是UsernamePasswordAuthenticationFilter
                .and().formLogin().usernameParameter("userName").passwordParameter("password").loginProcessingUrl("/user/login")
                .successHandler(successHandler).failureHandler(failureHandler)

                //确保再token过滤器之前authentication已经存在
                .and()
                .addFilterBefore(tokenAuthenticationFilter(), LogoutFilter.class)

                //登出配置，默认是gete类型的logout
                .logout().logoutUrl("/user/logout").addLogoutHandler(logoutHandler)

                //授权配置
                .and().authorizeRequests().antMatchers(properties.getWhiteList().toArray(new String[0])).permitAll().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().anyRequest()
                //.authenticated() //authenticated  登录即可访问所有的接口，不能细粒度的控制 ==>spel 参数可以传WebSecurityExpressionRoot或其父类的字段
                .access("@rbacManager.hasPermission(request)")

                //异常处理配置(默认跳到登录页面) & 访问拒绝处理
                .and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)

        ;
    }


    private CorsConfigurationSource configurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许跨域的域名
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        // get,options,post...
        configuration.setAllowedMethods(Collections.singletonList("*"));
        // 单位:s,用来设置options请求(预检请求)在客户端的缓存时间
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 允许跨域访问的url
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
